import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.domain.FIPAException;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import java.util.*;

public class ReceiveAgent extends Agent
{
  protected Container [] Receivers;
  protected String bpName;
  protected String here;
  protected boolean doneSent;
  protected int MAX_RANGE;
  protected int curr_time;
  protected int maxX;
  protected int maxY;
  protected int maxZ;
  protected int time_stamp;
  protected int totalContainers;
  

  public void setup()
  {
    here = here().getName();
    System.out.println("Receive Agent at " + here().getName());
    DFAgentDescription dfd = new DFAgentDescription();
    dfd.setName(getAID());
    ServiceDescription sd = new ServiceDescription();
    sd.setType("Message-receiving");
    sd.setName(getLocalName()+"-Message-receiving");
    dfd.addServices(sd);
    try
    {
      DFService.register(this, dfd);
    }
    catch (FIPAException fe)
    {
      fe.printStackTrace();
    }
    
    addBehaviour( new CyclicBehaviour()
    {
      public void action()
      {
        MessageTemplate tmp = MessageTemplate.MatchOntology("Container-start" +
          "-ontology");

        ACLMessage firstMsg = receive(tmp);
        if (firstMsg != null)
        {
          
          StringTokenizer st = new StringTokenizer(firstMsg.getContent(),":");
          maxX = Integer.parseInt(st.nextToken());
          maxY = Integer.parseInt(st.nextToken());
          maxZ = Integer.parseInt(st.nextToken());
          totalContainers = Integer.parseInt(st.nextToken());
          bpName = st.nextToken();
          time_stamp = Integer.parseInt(st.nextToken());
          Receivers = new Container[maxX * maxY * maxZ];
          for (int i = 0; i < Receivers.length; i++)
          {
            int iClone = i;
            int locz = iClone%maxZ;
            iClone /= maxZ;
            int locx = iClone%maxX;
            iClone /= maxX;
            int locy = iClone;
            Receivers[i] = new Container("Container_" + here + "_" + i, locx,
                locy, locz, 0, Math.round(Math.random()*90), 90L, false, false);
          }

          //-------------------- OTHER BEAHAVIOURS ------------------------
          System.out.println("Receive Agent's here too!");
          addBehaviour( new CyclicBehaviour()
          {
         
            public void action()
            {
              distributeCrateMessages();
              Cycle(time_stamp);
              CheckMessages();
            }
          });

          removeBehaviour(this);
        }
        else
        {
          block();
        }
      }
    });

  }

  private void Cycle(int time_stamp)
  {
    if (curr_time < time_stamp)
    {
      doneSent = false;
      for (int i = 0; i < Receivers.length; i++)
      {
        process(Receivers[i]);
      }
    }
    else if (curr_time == time_stamp && !doneSent)
    {
      ACLMessage doneMsg = new ACLMessage(ACLMessage.INFORM);
      doneMsg.addReceiver(new AID("local_" + here, AID.ISLOCALNAME));
      doneMsg.setLanguage("English");
      doneMsg.setOntology("Container-continue-ontology");
      doneMsg.setContent("Done");
      send(doneMsg);
      doneSent = true;
    }
  }

  private void CheckMessages()
  {
    ACLMessage msgRX = receive();
    while (msgRX != null)
    {
      process(msgRX);
      msgRX = receive();
    }
  }

  private void distributeCrateMessages()
  {
    MessageTemplate tmp = MessageTemplate.MatchOntology("Container-" +
        "message-ontology");
    ACLMessage cmo = receive(tmp);
    while (cmo != null)
    {
      System.out.println(cmo);
      StringTokenizer st = new StringTokenizer(cmo.getContent(),"|");
      int sender = hashloc(Integer.parseInt(st.nextToken()),
          Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken()));
      Set<Container> ContainerRecipients = traverseContainers(MAX_RANGE, sender);

      Iterator<Container> it = ContainerRecipients.iterator();
      String cms = st.nextToken();
      while (it.hasNext())
      {
        it.next().receiveMessage(cms);
      }
      //parse the content of the message. One of these items will need to be
      //the crate's current location
      /*
       * Crates CrateRecipients = new Crates[2*MAX_RANGE*(MAX_RANGE+1)+2];
       *
       *
       * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       * Found to be true by calculating the range of crates in the following
       * setup:          |_|
       *               |_|_|_|
       *                 |_|
       * and propagating that outwards.  The additional (+2) at the end
       * allows for traversal along the y-axis
       * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       *
       *
       * traverseContainers(MAX_RANGE, currLocation);
       *
       * for (int i = 0; i < CrateRecipients.length; i++)
       * {
       *   if (CrateRecipients[i] != null)
       *   {
       *     CrateRecipients[i].addMessage(cmo.getContent());
       *   }
       * }
       */
      cmo = receive(tmp);
    }
  }
  
  private Set<Container> traverseContainers(int RANGE, int currLocation)
  {
    int [] loc = dehash(currLocation);
    //WRITE DEHASH
    //WRITE HASH
    Set<Container> list = new HashSet<Container>();
    if (currLocation >= 0 && currLocation < Receivers.length &&
        Receivers[currLocation] != null)
    {
      list.add(Receivers[currLocation]);
    }
    if (RANGE > 0 )
    {
      if (loc[0]+1 < maxX)
      {
        list.addAll(traverseContainers(RANGE-1,currLocation+maxZ));
      }
      if (loc[0]-1 >= 0)
      {
        list.addAll(traverseContainers(RANGE-1,currLocation-maxZ));
      }
      if (loc[2]+1 < maxZ)
      {
        list.addAll(traverseContainers(RANGE-1,currLocation+1));
      }
      if (loc[2]-1 >= 0)
      {
        list.addAll(traverseContainers(RANGE-1,currLocation-1));
      }
      if (RANGE == MAX_RANGE)
      {
        if (loc[1]+1 < maxY)
        {
          list.addAll(traverseContainers(RANGE-1,currLocation+maxZ*maxY));
        }
        if (loc[1]-1 >= 0 )
        {
          list.addAll(traverseContainers(RANGE-1,currLocation-maxZ*maxY));
        }
      }
    }
    return list;
  }

  private int hashloc(int x, int y, int z)
  {
    return maxZ*(maxX*y + x) + z;
  }
  private int [] dehash(int loc)
  {
    int [] dLoc = new int[3];
    dLoc[2] = loc%maxZ;
    loc /= maxZ;
    dLoc[0] = loc%maxX;
    loc /= maxX;
    dLoc[1] = loc;
    return dLoc;
  }

  private void process(Container obj)
  {
    if (obj != null)
    {
      obj.processEnvAndState();
      obj.incrementClock();
      if (obj.CLOCK_TIME % obj.CYCLE_LENGTH > obj.RECEIVE_TIME_2)
      {
        ACLMessage crate = new ACLMessage(ACLMessage.INFORM);
        crate.addReceiver(new AID("Sleep_" + here().getName() + "_" + bpName,
             AID.ISLOCALNAME));
        crate.setLanguage("English");
        crate.setOntology("Container-delivery-ontology");
        crate.setContent(obj.toMessage("|"));
        send(crate); //SEND TO SLEEP AGENT
        Receivers[hashloc(obj.location[0],obj.location[1],obj.location[2])] =
          null;
      }
      else if (obj.CLOCK_TIME% obj.CYCLE_LENGTH > obj.SLEEP_TIME && 
          obj.CLOCK_TIME % obj.CYCLE_LENGTH < obj.SEND_TIME)
      {
        ACLMessage crate = new ACLMessage(ACLMessage.INFORM);
        crate.addReceiver(new AID("Send_" + here().getName() + "_" + bpName,
             AID.ISLOCALNAME));
        crate.setLanguage("English");
        crate.setOntology("Container-delivery-ontology");
        crate.setContent(obj.toMessage("|"));
        send(crate); //SEND TO SEND AGENT
        Receivers[hashloc(obj.location[0],obj.location[1],obj.location[2])] =
          null;
      }
    }
  }
  
  private void process(ACLMessage msg)
  {
    MessageTemplate tmp = MessageTemplate.MatchOntology("Container-continue-" +
        "-ontology");
    ACLMessage rcv = receive(tmp);
    if (rcv != null)
    {
      time_stamp = Integer.parseInt(rcv.getContent());
    }

  }


  //Any further functions that the ReceiveAgent requires will go here.
}
      
